/**
 * 
 */
package edu.gatech.oad.mule.model;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;

import javax.swing.Timer;

import edu.gatech.oad.mule.Bar;
import edu.gatech.oad.mule.Exit;
import edu.gatech.oad.mule.Game;
import edu.gatech.oad.mule.Map;
import edu.gatech.oad.mule.Player;
import edu.gatech.oad.mule.RandomEvent;
import edu.gatech.oad.mule.Tile;
import edu.gatech.oad.mule.WorldMap;
import edu.gatech.oad.mule.Zone;
import edu.gatech.oad.mule.RandomEvent.eventConstraint;
import edu.gatech.oad.mule.input.InputEvent;
import edu.gatech.oad.mule.input.InputEvent.ButtonPress;
import edu.gatech.oad.mule.input.interfaces.ControllerOwner;
import edu.gatech.oad.mule.model.GameStateMachine.GameState;
import edu.gatech.oad.mule.view.interfaces.RenderableMap;
import edu.gatech.oad.mule.view.interfaces.RenderablePlayer;
import edu.gatech.oad.mule.view.interfaces.RenderableTile;
import edu.gatech.oad.mule.view.interfaces.RenderableTile.TileType;

/**
 * DevelopmentPhase handles the logic for a single round's development phase.
 * 
 * @author Dale Avery
 * 
 */
final class DevelopmentPhase extends Phase {

	private static final long serialVersionUID = -8660574354644612987L;

	private static final GameState STATE = GameState.DEVELOPMENT;

	private static final int TIMER_PERIOD = 100;
	private static final int PLAYER_MOVEMENT_SPEED = 5;
	private static final int PLAYER_TURN_LEN_ZERO = 5000;
	private static final int PLAYER_TURN_LEN_SHORTAGE = 30000;
	private static final int PLAYER_TURN_LEN_REG = 50000;

	private ArrayList<Player> remainingPlayers;
	private Player currentPlayer;
	private Map currentMap;
	private Timer timer;
	private transient ActionListener listener;
	private int timeCounter;
	private int currentTurn;

	/**
	 * Constructs a new DevelopmentPhase.
	 * 
	 * @param game
	 *            Object used to obtain relevant information for phase.
	 */
	public DevelopmentPhase(Game game) {
		super(game);

		// Initialize timer for controlling turns
		timer = new Timer(TIMER_PERIOD, null);
	}

	/**
	 * Helper method for determining the length of a player's turn.
	 * 
	 * @param food
	 *            The amount of food a player has.
	 * @param round
	 *            The current round of the game (starting at 1).
	 * @return The length of the turn in milliseconds.
	 */
	private int calcTurnLength(int food, int round) {
		// Calculate food requirement based on game round.
		int requiredFood;
		if (round <= 4)
			requiredFood = 3;
		else if (round <= 8)
			requiredFood = 4;
		else
			requiredFood = 5;

		// Calculate length based on food requirements.
		if (food <= 0)
			return PLAYER_TURN_LEN_ZERO;
		else if (food < requiredFood)
			return PLAYER_TURN_LEN_SHORTAGE;
		else
			return PLAYER_TURN_LEN_REG;
	}

	/**
	 * Keeps track of the current turn and makes sure the phase transitions
	 * between player turns and ends once all players have had a turn.
	 */
	private void checkTime() {
		timeCounter += timer.getDelay();

		// Check to see if the current turn is over.
		if (timeCounter >= currentTurn) {
			if (!remainingPlayers.isEmpty()) {
				nextPlayer();
				timeCounter = 0;
			} else {
				// No more players, end the phase.
				stop();
				endPhase();
			}
		}
	}

	@Override
	public ControllerOwner[] getActiveOwners() {
		if (currentPlayer == null) {
			return new ControllerOwner[0];
		}

		ControllerOwner[] arr = new ControllerOwner[1];
		arr[0] = currentPlayer;
		return arr;
	}

	@Override
	public RenderablePlayer[] getAllPlayers() {
		return game.getPlayers();
	}

	@Override
	public GameState getCorrespondingState() {
		return STATE;
	}

	@Override
	public RenderableMap getCurrentMap() {
		return (RenderableMap) currentMap;
	}

	@Override
	public RenderablePlayer getCurrentPlayer() {
		return currentPlayer;
	}

	@Override
	public RenderableTile getCurrentTile() {
		// Selected tile is irrelevant in development phase.
		return null;
	}

	@Override
	public RenderablePlayer[] getRankedPlayers() {
		return game.getRankedPlayers();
	}

	@Override
	public int getRemainingTime() {
		return currentTurn - timeCounter;
	}

	@Override
	public int getTurnLength() {
		return currentTurn;
	}

	@Override
	public void inputEventHandler(InputEvent event) {
		// Player movement.
		if (event.getPress() == ButtonPress.UP) {
			currentPlayer.move(0, -PLAYER_MOVEMENT_SPEED);

		} else if (event.getPress() == ButtonPress.DOWN) {
			currentPlayer.move(0, PLAYER_MOVEMENT_SPEED);

		} else if (event.getPress() == ButtonPress.LEFT) {
			currentPlayer.move(-PLAYER_MOVEMENT_SPEED, 0);

		} else if (event.getPress() == ButtonPress.RIGHT) {
			currentPlayer.move(PLAYER_MOVEMENT_SPEED, 0);

			// Player interaction.
		} else if (event.getPress() == ButtonPress.ACTION) {
			System.out
					.println("Player should interact with environment now at "
							+ currentPlayer.getLocation());
			Zone zone = currentMap.getCollidedZone(currentPlayer);

			if (zone != null) {
				if (zone instanceof Tile) {
					// Player is on world map.
					Tile tile = (Tile) zone;

					if (tile.getTileType() == TileType.TOWN) {
						// Move player into town map.
						currentMap = game.getTownMap();
						currentPlayer.setLocation(WorldMap.WIDTH / 2,
								WorldMap.HEIGHT / 2);

					} else {
						// Just a regular tile, it will handle interaction.
						tile.interact(currentPlayer, STATE, game.getRound(),
								(timer.getDelay() - timeCounter));
					}

					// Interactions in town.
				} else if (zone instanceof Bar) {
					// Interacting with Bar should end the player's turn and
					// transition to next player's turn
					((Bar) zone).interact(currentPlayer, STATE,
							game.getRound(), (currentTurn - timeCounter));
					nextPlayer();

				} else if (zone instanceof Exit) {
					// Interacting with an exit in the town should transition to
					// the world map.
					currentMap = game.getWorldMap();
					System.out.println("map changed to "
							+ ((RenderableMap) currentMap).getMapType());
					currentPlayer.setLocation(WorldMap.WIDTH / 2,
							WorldMap.HEIGHT / 2);
				} else {
					zone.interact(currentPlayer, STATE, game.getRound(),
							(currentTurn - timeCounter));
				}
			}
		}
	}

	/**
	 * Transitions from current player to next player in the ranked player list.
	 * Ends the phase once all players have had a turn.
	 */
	private void nextPlayer() {
		if (!remainingPlayers.isEmpty()) {
			currentPlayer = remainingPlayers.remove(0);

			// Apply random event to next player.
			if (currentPlayer.equals(game.getRankedPlayers()[0])) {
				RandomEvent.distribute(currentPlayer, game.getRound(),
						eventConstraint.GOOD);
			} else if (currentPlayer.equals(game.getRankedPlayers()[game
					.getRankedPlayers().length - 1])) {
				RandomEvent.distribute(currentPlayer, game.getRound(),
						eventConstraint.BAD);
			} else {
				RandomEvent.distribute(currentPlayer, game.getRound(),
						eventConstraint.NONE);
			}

			// Set up timer for a new player turn.
			currentTurn = calcTurnLength(currentPlayer.getFood(),
					game.getRound() + 1);
			timer.stop();
			timer.restart();
			timer.start();

			// Set player in the center of town map.
			currentMap = game.getTownMap();
			// TODO make map store its location instead of using RenderableMap.
			int x = ((RenderableMap) currentMap).getSize().width / 2;
			int y = ((RenderableMap) currentMap).getSize().height / 2;
			currentPlayer.setLocation(x, y);
		} else {
			stop();
			endPhase();
		}
	}

	@Override
	public void reset() {
		// Reset the timer.
		stop();
		timer.restart();
		timeCounter = 0;

		// Destroy existing phase player data.
		remainingPlayers = null;
		currentPlayer = null;
		currentMap = null;
	}

	@Override
	public void start() {
		// Configure player information (if necessary).
		if (remainingPlayers == null) {
			remainingPlayers = new ArrayList<Player>(Arrays.asList(game
					.getRankedPlayers()));
		}
		if (currentPlayer == null) {
			nextPlayer();
		}

		// Configure timer.
		if (listener == null) {
			listener = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					checkTime();
				}
			};
		}

		// Only add the action listener if it isn't already added.
		if (timer.getActionListeners().length == 0)
			timer.addActionListener(listener);

		timer.start();
		active = true;
	}

	@Override
	public void stop() {
		// Stop the timer to pause the phase.
		active = false;
		timer.stop();
		// Remove action listeners for serialization.
		for (ActionListener l : timer.getActionListeners())
			timer.removeActionListener(l);
	}

}
